Skip to content

Verify the packed worker-bundler at build time - #1798

Merged
RhysSullivan merged 1 commit into
mainfrom
fix/desktop-daemon-bundled-module
Aug 28, 2026
Merged

Verify the packed worker-bundler at build time#1798
RhysSullivan merged 1 commit into
mainfrom
fix/desktop-daemon-bundled-module

Conversation

@RhysSullivan

@RhysSullivan RhysSullivan commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator

Problem

The compiled executor binary — the same binary the desktop app ships as its daemon — cannot resolve @cloudflare/worker-bundler by name. bun build --compile puts JS in bunfs, which has no node_modules, so a bare specifier evaluated inside the binary throws Cannot find module '@cloudflare/worker-bundler' at startup. The package is also unembeddable in principle: its payload is a 13.9 MB esbuild.wasm that is read from disk, not imported.

The delivery mechanism for that is colocation: build.ts copies the package's dist/ next to the executable, and native-bindings.ts publishes the absolute path as EXECUTOR_WORKER_BUNDLER_DIR for consumers to load from. That handoff was only half-built.

  • The required file list existed in two places, free to drift — and had. The build writes dist/index.bundled.js (the entry consumers load, packed so it has no bare imports of its own), while the runtime check only looked for dist/index.js and dist/esbuild.wasm.
  • Nothing verified the staged copy after the compile. build.ts validates its build inputs but made no assertion about the bin/ directory it produced, so a partial staging shipped a binary that was fine on the build machine and broken on the user's.
  • The runtime check failed open. When a file was missing it silently skipped setting the env var, leaving any consumer to fall through to the bare specifier and crash on startup. That silent skip is what turns a packaging slip into a crash loop.

For context on scope: the last consumer of this module was removed in #1476, which deliberately kept the packing for when it returns. So this is the delivery contract being made sound before something depends on it again, not a fix for a live import.

Fix

  • New apps/cli/src/worker-bundler-artifact.ts holds the required-file list as a single shared contract, plus a pure missingWorkerBundlerFiles(dir, exists) that takes its existence probe as a parameter. The build script and the runtime bootstrap both use it, so they cannot disagree. This also corrects the list to include dist/index.bundled.js.
  • build.ts asserts the staged copy after compiling each target: every required file present, a size floor on the packed entry, and the \0asm magic on the wasm so a truncated or pointer-file copy cannot pass as real. A packaging slip is now a red build instead of a broken install. This follows the shape of the existing asset assertions in apps/cloud/scripts/build.mjs and apps/host-selfhost/scripts/assert-shell-asset.mjs.
  • native-bindings.ts no longer swallows a bad copy. A directory that is present but incomplete is reported on stderr naming the missing files and telling the user how to repair it — not caught and ignored. A directory that is absent entirely stays quiet, since that is the normal non-packaged path (dev, bun run).

Testing

typecheck, lint (0 warnings, 0 errors) and format are green.

Unit — apps/cli/src/worker-bundler-artifact.test.ts, 6 tests. These are the build-time guard the contract needs: they fail if a required file is dropped from the list, specifically covering the two historical drift cases (wasm staged without the JS entries, and the unbundled index.js staged without index.bundled.js).

Built the real daemon binary and ran it, rather than relying on file paths:

  • bun ./scripts/build-sidecar.ts from apps/desktop compiles the CLI binary and stages it — the new post-compile assertion passes against a correct staging.

  • bun run test:smoke (smoke-sidecar.ts) drives the compiled binary end to end: daemon ready, 1Password SDK loads, and listPets + getPet round-trip through a live OpenAPI server via MCP to QuickJS.

  • Degradation path proven by tampering rather than asserted: with worker-bundler/dist/esbuild.wasm moved aside, the daemon still reaches ready and prints

    executor: the bundled Worker toolchain at <path> is incomplete (missing dist/esbuild.wasm). Features that build Workers will be unavailable; reinstall or update executor to repair it.

    With the file restored, it starts clean with no such message. Before this change the same tampering produced no output at all.

Platform limitation, stated plainly: all of the above was run on macOS arm64, not Windows. Desktop e2e targets were out of scope. This is a fair proxy but not identical coverage: the compile and staging path is genuinely shared — one Bun.build({ compile: { target } }) loop, and the worker-bundler copy is unconditional for every target — so the assertion and the shared contract behave the same everywhere. What macOS cannot exercise is the Windows-specific packaging leg (NSIS extraResources) and, more importantly, that native-bindings.ts anchors on dirname(process.execPath): if a Windows install ever launches a relocated executor.exe, every colocated sibling is lost at once, and this change makes that visible rather than fixing it. A cross-build (BUN_TARGET=bun-windows-x64) compiles but cannot be run or smoke-tested from macOS.

…te copy

The compiled binary cannot resolve @cloudflare/worker-bundler by name, so the
build stages the package dist beside the executable and native-bindings
publishes its path. The required file list was duplicated across those two
sides and had drifted: the build writes dist/index.bundled.js while the runtime
check only looked for dist/index.js and dist/esbuild.wasm.

Make the list one shared contract, assert the staged copy after compiling each
target, and report a present-but-incomplete directory on stderr instead of
silently declining to publish the path.
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 28, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
executor-marketing 871690f Commit Preview URL

Branch Preview URL
Aug 28 2026, 03:36 AM

@github-actions

github-actions Bot commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Cloudflare preview

Torn down — the PR is closed.

@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
executor-cloud 871690f Aug 28 2026, 03:36 AM

@pkg-pr-new

pkg-pr-new Bot commented Aug 28, 2026

Copy link
Copy Markdown

Open in StackBlitz

@executor-js/cli

npm i https://pkg.pr.new/@executor-js/cli@1798

@executor-js/config

npm i https://pkg.pr.new/@executor-js/config@1798

@executor-js/execution

npm i https://pkg.pr.new/@executor-js/execution@1798

@executor-js/sdk

npm i https://pkg.pr.new/@executor-js/sdk@1798

@executor-js/codemode-core

npm i https://pkg.pr.new/@executor-js/codemode-core@1798

@executor-js/runtime-quickjs

npm i https://pkg.pr.new/@executor-js/runtime-quickjs@1798

@executor-js/plugin-file-secrets

npm i https://pkg.pr.new/@executor-js/plugin-file-secrets@1798

@executor-js/plugin-graphql

npm i https://pkg.pr.new/@executor-js/plugin-graphql@1798

@executor-js/plugin-keychain

npm i https://pkg.pr.new/@executor-js/plugin-keychain@1798

@executor-js/plugin-mcp

npm i https://pkg.pr.new/@executor-js/plugin-mcp@1798

@executor-js/plugin-onepassword

npm i https://pkg.pr.new/@executor-js/plugin-onepassword@1798

@executor-js/plugin-openapi

npm i https://pkg.pr.new/@executor-js/plugin-openapi@1798

executor

npm i https://pkg.pr.new/executor@1798

commit: 871690f

@RhysSullivan
RhysSullivan marked this pull request as ready for review August 28, 2026 03:43
@RhysSullivan
RhysSullivan merged commit 69b0e64 into main Aug 28, 2026
44 checks passed
@RhysSullivan RhysSullivan mentioned this pull request Aug 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant